// sprite rendering and xml parsing unit

function SpriteImageset(AParentNode,ARed,AGreen,ABlue) {
  // parse xml.sprite.imageset[]
  var node = AParentNode.getElementsByTagName('imageset');
  var that = new Array();
  for (var i=0; i<node.length; i++) {
    that[i] = new Object();
    that[i].name = node[i].getAttribute('name');
    that[i].src = node[i].getAttribute('src');
    if (that[i].src.indexOf('|') > 0)
      that[i].src = that[i].src.split('|')[0];
    that[i].width = 1*node[i].getAttribute('width');
    that[i].height = 1*node[i].getAttribute('height');
    that[i].image = new Image();
    that[i].image.bw = that[i].width; // we need base width/height in image for scope of onload event 
    that[i].image.bh = that[i].height;
    that[i].image.imagesetindex = i;
    that[i].image.onload = function() {
      this.modx = Math.floor(this.width / this.bw);
      this.mody = Math.floor(this.height / this.bh);
      //debug('  imageset['+this.imagesetindex+']: mod='+this.modx+'/'+this.mody);
      // remove loaded image from loader
      loader.pop(this.src);
    } 
    // simple image loader (no colors)
    if ( (ARed<=0)&&(AGreen<=0)&&(ABlue<=0) ) {
      loader.push(that[i].src);
			that[i].image.src = that[i].src;
    }	else {
			// dyeable image loader - via colorize.php
			var s = 'colorize.php?r='+ARed+'&g='+AGreen+'&b='+ABlue+'&image='+that[i].src.replace('graphics/sprites/','').replace('.png','');
      loader.push(s);
			that[i].image.src = s;
			///debug(' dyeable src='+that[i].image.src)
		}
    that[i].info = function(AIndexHint) {
      var s = ' imageset['+AIndexHint+']: name='+this.name+' width='+this.width+' height='+this.height+'\n image='+this.image.width+','+this.image.height+' mod='+this.image.modx+','+this.image.mody+'\n  src='+this.src+' \n';
      return s;
    }
  }
  return that;
}

function SpriteActionAnimationFrame(AParentNode) {
  // parse xml.sprite.action[].animation[].frame[]
  var that = new Array();
  var t = 0;
  // get both frame and sequence nodes at once to sf[] so it keeps the order even if frame and both sequence is used
  var sf = AParentNode.childNodes;
  for (var i=0; i<sf.length; i++) {
    // ignore text nodes
    if (sf[i].nodeName == '#text')
      continue;
    //debug('sf['+i+'] = '+sf[i].nodeName);
    // frame
    if (sf[i].nodeName == 'frame') {
      that[t] = new Object();
      that[t].index = 1*sf[i].getAttribute('index');
      that[t].offsetX = 1*sf[i].getAttribute('offsetX');
      that[t].offsetY = 1*sf[i].getAttribute('offsetY');
      that[t].delay = 1*sf[i].getAttribute('delay');
      // inherited from parent (parent attributes cannot be loaded in parent because we use arrays but node is object + contain child nodes, otherwise i had to use action.name, action.items[].foo, but i want to avoid items[] and simply use action[].foo)
      //that[i].direction = AParentNode.getAttribute('direction');
      //debug('---- frame['+i+']: index='+that[i].index+' offseetX='+that[i].offsetX+' offseetY='+that[i].offsetY+' delay='+that[i].delay);
      that[t].info = function(AIndexHint) {
        var s  = '   frame['+AIndexHint+']: index='+this.index+' offseetX='+this.offsetX+' offseetY='+this.offsetY+' delay='+this.delay+'\n'; // direction='+this.direction+'\n';
        return s;
      }
      // next frame
      t++;
    }
    // sequence
    if (sf[i].nodeName == 'sequence') {
      // convert sequence to frames
      var start = 1*sf[i].getAttribute('start');
      var end = 1*sf[i].getAttribute('end');
      var delay = 1*sf[i].getAttribute('delay');
      // 1 sequence = (end-start+1) frames
      for (var z=start; z<=end; z++) {
        that[t] = new Object();
        that[t].index = z;
        that[t].offsetX = 1*sf[i].getAttribute('offsetX');
        that[t].offsetY = 1*sf[i].getAttribute('offsetY');
        that[t].delay = delay;
        //
        that[t].info = function(AIndexHint) {
          var s  = '   s-frame['+AIndexHint+']: index='+this.index+' offseetX='+this.offsetX+' offseetY='+this.offsetY+' delay='+this.delay+'\n'; // direction='+this.direction+'\n';
          return s;
        }
        //  next frame
        t++;
      }
    }
  }
  return that;
}

function SpriteActionAnimation(AParentNode) {
  // parse xml.sprite.action[].animation[]
  var node = AParentNode.getElementsByTagName('animation');
  var that = new Array();
  for (var i=0; i<node.length; i++) {
    //debug('--- animation['+i+']:'); // direction='+that[i].direction+' name='+that[i].name+' imageset='+that[i].imageset);
    that[i] = new SpriteActionAnimationFrame(node[i]);
    // attributes from node itself
    that[i].direction = node[i].getAttribute('direction');
    //debug('--- name='+that[i].name+' imageset='+that[i].imageset);
    // auxiliary debug function
    that[i].info = function(AIndexHint) {
      var s  = '  animation['+AIndexHint+'] ('+this.length+' frames, direction='+this.direction+'):\n';
      for (var i=0; i<this.length; i++)
        s += this[i].info(i);
      return s;
    }
  }

  that.getAnimation = function(ADirection) {
    // find animation by direction
    var def = null;
    for (var i=0; i<this.length; i++) {
      if (this[i].direction == ADirection)
        return this[i];
      if (this[i].direction == "default")
        def = this[i];
    }
    return def;
  }

  return that;
}

function SpriteAction(AParentNode) {
  // parse xml.sprite.action[]
  var node = AParentNode.getElementsByTagName('action');
  var that = new Array();
  for (var i=0; i<node.length; i++) {
    //debug('-- action['+i+']:');// name='+that[i].name+' imageset='+that[i].imageset);
    that[i] = new SpriteActionAnimation(node[i]);
    // atributes from node itself
    that[i].name = node[i].getAttribute('name');
    that[i].imageset = node[i].getAttribute('imageset');

    // auxiliary debug function
    that[i].info = function(AIndexHint) {
      var s = ' action['+AIndexHint+'] ('+this.length+' animations, name=<b>'+this.name+'</b>):\n';
      for (var i=0; i<this.length; i++)
        s += this[i].info(i);
      return s;
    }
    
  }
  
  that.getAction = function(AName) {
    // find action by name
    //alert('hladam '+AName);    
    for (var i=0; i<this.length; i++)
      if (this[i].name == AName) {
        //alert('mam '+AName);    
        return this[i];
      }
    return null;
  }
  
  return that;
}

function Sprite(AXmlFileName,ARed,AGreen,ABlue) {
  // load sprite XML specification file
  //debug('Sprite('+AXmlFileName+')');
  this.filename = AXmlFileName;
  this.parsed = false;
  this.weird = weird[AXmlFileName];
  this.red = ARed;
  this.green = AGreen;
  this.blue = ABlue;
  if (!this.weird) {
		this.weird = weird["unknown"];
		//debug('Sprite "'+AXmlFileName+'" has no known weird offsets, it might be displayed incorrectly!');
	}
  
  this.parse = function() {
    if (this.parsed)
      return;
    //debug('Sprite.parse() for "'+AXmlFileName+'"');
    var xml = sjax(AXmlFileName);
    // include files
    // NOTE: for now only actions and include are added, not imageset (this is intended behaviour)
    var include_count = 1;
    while (include_count > 0) {
      var sprite = xml.documentElement;
      var include = sprite.getElementsByTagName('include');
      //alert('include.length = '+include.length);
      include_count = 0;
      for (var i=0; i<include.length; i++) {
        // which file to include?
        var fname = 'graphics/sprites/'+include[i].getAttribute('file');
        // load including file
        var x = sjax(fname);
        var xs = x.documentElement;
        var xa = xs.getElementsByTagName('action');
        //alert(xs.nodeName+' has childNodes: '+xs.childNodes.length);
        //alert('actions found: '+xa.length);
        for (var j=xa.length-1; j>=0; j--) {
          //alert('pridavam['+j+'/'+xa.length+']: '+xa[j].nodeName+' name='+xa[j].getAttribute('name'));
          var n = xa[j].cloneNode(true);
          xml.documentElement.appendChild(n);
          //xml.documentElement.appendChildcloneNode(xa[j]);
        }
        // include another include too
        var xi = xs.getElementsByTagName('include');
        for (var j=xi.length-1; j>=0; j--) {
          var n = xi[j].cloneNode(true);
          xml.documentElement.appendChild(n);
        }
        // mark current inclusion as used
        include[i].used = true;
      }
    }
    // parse xml 
    var sprite = xml.documentElement;
    // parse xml
    this.imageset = new SpriteImageset(sprite,ARed,AGreen,ABlue);
  //  this.imageset[0].loadImage();
    this.action = new SpriteAction(sprite);
    // BUG: i must remember weird offset random values because hats and other stuff are randomly shifted and I really don't know how and why
    this.weirdx = this.weird[0];
    this.weirdy = this.weird[1];
    this.parsed = true;
  }
  
  this.info = function() {
    var s = 'Sprite ('+this.imageset.length+' imagesets, '+this.action.length+' actions, filename='+this.filename+'):\n';
    for (var i=0; i<this.imageset.length; i++) 
      s += this.imageset[i].info(i);
    for (var i=0; i<this.action.length; i++) 
      s += this.action[i].info(i);
    return s;
  }
  
  this.setActionAnimation = function(AName,ADirection) {
    // set action and animation
    this.parse();
    // find action
    this.current_action = this.action.getAction(AName);
    //alert('ca.name = '+this.current_action.name);
    // find animation
    this.current_animation = this.current_action.getAnimation(ADirection);
    //alert('can.direction = '+this.current_animation.direction);
  }
  
  this.draw = function(ALeft,ATop,ACurrentAnimation) {
    // basic drawing function
    this.parse();
    if (this.imageset[0].image.modx <= 0)
      return;
    if (!ACurrentAnimation) // opera
      return;
    var animation_index = frame % (ACurrentAnimation.length); 
    var index = ACurrentAnimation[animation_index].index;
    //var animation_index = frame % (this.current_animation.length); 
    //var index = this.current_animation[animation_index].index;
    var oy = Math.floor(index / this.imageset[0].image.modx);
    var ox = index - oy*this.imageset[0].image.modx;
    // draw image
    if (ox == 1*ox)
      try {
        context.drawImage(this.imageset[0].image,
          ox*this.imageset[0].width,
          oy*this.imageset[0].height,
          this.imageset[0].width,
          this.imageset[0].height,
          ALeft+ACurrentAnimation[animation_index].offsetX+this.weirdx,
          ATop+ACurrentAnimation[animation_index].offsetY+this.weirdy,
          //ALeft+this.ACurrentAnimation[animation_index].offsetX+this.weirdx,
          //ATop+this.ACurrentAnimation[animation_index].offsetY+this.weirdy,
          this.imageset[0].width,
          this.imageset[0].height
        );
      } catch(e) {
        debug(e);
        debug('ox='+ox+' oy='+oy+' is='+this.imageset[0]);
      }
  }
}

function MetaSprite(AEqty,ARace) {
  // create meta sprite - use reference to original sprite but remembers current action/animation here so 2 MetaSprites can have different action/animation but shares the same sprite
  if (!ARace)
    throw('MetaSprite() - undefined race');
  this.sprite = null;
  if ( (AEqty == 1*AEqty)&&(AEqty!=0) ) {
    // lookup sprite for given race or "any" if race is not available
    var sp = sprite_lookup[AEqty+'_'+ARace];
    if (!sp)
      sp = sprite_lookup[AEqty+'_any'];
    if (!sp)
      throw "MetaSprite: failed to lookup sprite "+AEqty+" for race '"+ARace+"' nor 'any'";
    ///debug('  metasprite: fn='+sp.sprite.filename+' rgb='+sp.red+','+sp.green+','+sp.blue);
    this.sprite = sp.sprite;
  }
	this.eqty = AEqty;
	this.action = null;
  this.animation = null;
  
  this.update = function(AAction,AAnimation) {
    if (!this.sprite)
      return;
      //throw('MetaSprite.sprite undefined, action='+AAction+' animation='+AAnimation);
    this.sprite.parse();
    this.action = this.sprite.action.getAction(AAction);
    this.animation = this.action.getAnimation(AAnimation);
  }
  
  this.draw = function(ALeft,ATop) {
    if (!this.sprite)
      return;
    this.sprite.parse();
    this.sprite.draw(ALeft,ATop,this.animation);
  }
}

function SpriteLookup(AXtrUrl) {
  // create server sprites lookup table 
  var that = new Array();
  
  // load xtr
  ///debug('SpriteLookup from "'+AXtrUrl+'"');
  var xtr = new PuXtr(AXtrUrl,null);
  if (xtr.code != 0)
    alert('Cannot load server sprites "'+AXtrUrl+'" - '+sta[1]);
  
  // parse xtr
  for (var i=0; i<xtr.row.length; i++) {
    // <row i_spri="1" j_eqty="-1" v_race="any" v_xml="hairstyle1.xml" />
    //that[i] = new Object();
    //that[i].eqty = xtr.row[i].getAttribute('j_eqty');
    //that[i].race = xtr.row[i].getAttribute('v_race');
    //that[i].xml = xtr.row[i].getAttribute('v_xml');
    var index = xtr.row[i].getAttribute('j_eqty')+'_'+xtr.row[i].getAttribute('v_race');
    that[index] = new Object();
    that[index].xml = 'graphics/sprites/'+xtr.row[i].getAttribute('v_xml');
    that[index].red = (xtr.row[i].getAttribute('i_red') > 0) ? xtr.row[i].getAttribute('i_red') : -1;
    that[index].green = (xtr.row[i].getAttribute('i_green') > 0) ? xtr.row[i].getAttribute('i_green') : -1;
    that[index].blue = (xtr.row[i].getAttribute('i_blue') > 0) ? xtr.row[i].getAttribute('i_blue') : -1;
    that[index].sprite = new Sprite(that[index].xml,that[index].red,that[index].green,that[index].blue);
  }
  ///debug('  loaded: '+xtr.row.length);
  
  // sample test
  //debug('  test: 546_male='+that[546+'_'+'male'].xml);
  
  return that;
}
